//  Copyright 2009 David Ekholm, Jalbum AB
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.package net.jalbum.jprint;
package net.jalbum.jprint;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import static java.awt.print.Printable.NO_SUCH_PAGE;
import static java.awt.print.Printable.PAGE_EXISTS;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.IOException;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.prefs.Preferences;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import se.datadosen.jalbum.AlbumBean;
import se.datadosen.jalbum.AlbumImage;
import se.datadosen.jalbum.AlbumObject;
import se.datadosen.jalbum.JAFilter;
import se.datadosen.jalbum.PluginContext;
import se.datadosen.util.Debug;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.ProgressMonitor;
import se.datadosen.component.ControlPanel;
import se.datadosen.component.StateMonitor;
import se.datadosen.util.Replacer;

/**
 * Prints selected Jalbum images onto a local printer
 *
 * @author david
 */
public class JPrintDialog extends JDialog implements Printable {

    static final int menuShortcutKeyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); // Convenience

    private static final ImageIcon bigIcon = new ImageIcon(JPrintSelectedPlugin.class.getResource("images/printer40.png"));

    private final Preferences prefs = Preferences.userNodeForPackage(this.getClass());
    private PluginContext context;
    private List<AlbumObject> images;

    private int gap = 12; // Image gap in 1/72"
    JCheckBox showFilenames = new JCheckBox("Show filenames", prefs.getBoolean("showFilenames", true));
    JCheckBox showCaptions = new JCheckBox("Show captions", prefs.getBoolean("showCaptions", false));
    JComboBox<PageLayout> pageLayout = new JComboBox<PageLayout>(PageLayout.values());
    JPageLayout pageLayoutIndicator;

    PrinterJob printJob;
    AlbumBean engine;
    PageFormat pageFormat;
    ProgressMonitor progressMonitor;

    private Replacer stripHtml = new Replacer();

    {
        stripHtml.add("<br>", "\n");
        stripHtml.add("<BR>", "\n");
        stripHtml.add("<b>", "");
        stripHtml.add("</b>", "");
    }

    private Action closeAction = new AbstractAction("Close") {

        public void actionPerformed(ActionEvent e) {
//            System.out.println("Closing");
            prefs.putInt("pageOrientation", pageFormat.getOrientation());
            prefs.put("pageLayout", getPageLayout().name());
            prefs.putBoolean("showFilenames", showFilenames.isSelected());
            prefs.putBoolean("showCaptions", showCaptions.isSelected());
            setVisible(false);
        }

    };

    private Action pageSetupAction = new AbstractAction("Page Setup...") {

        public void actionPerformed(ActionEvent e) {
            pageFormat = printJob.pageDialog(pageFormat);
            pageLayoutIndicator.setPageFormat(pageFormat);
            pageLayout.repaint();
        }
    };

    private void addFolder(AlbumObject folder) {
        for (AlbumObject ao : folder.getChildren()) {
            if (!ao.isIncluded()) {
                continue;
            }
            if (ao.isFolder()) {
                addFolder(ao);
            } else if (ao.hasDisplayableImage()) {
                images.add(ao);
            }
        }
    }

    private Action printAction = new AbstractAction("Print...") {

        public void actionPerformed(ActionEvent e) {
            printJob.setPrintable(JPrintDialog.this, pageFormat);
            if (printJob.printDialog()) {
                images = new ArrayList<AlbumObject>();
                AlbumObject[] all = context.getSelectedAlbumObjects();
                for (AlbumObject ao : all) {
                    if (!ao.isIncluded()) {
                        continue;
                    }
                    if (ao.isFolder()) {
                        addFolder(ao);
                    } else if (ao.hasDisplayableImage()) {
                        images.add(ao);
                    }
                }
//                System.out.println("Array size: " + all.length);
//                System.out.println("Content: " + Arrays.toString(all));
                progressMonitor = new ProgressMonitor(JPrintDialog.this, "Printing...", null, 0, images.size());
                pass = 1;
                new Thread("Print thread") {

                    @Override
                    public void run() {
                        try {
                            printJob.print();
                        } catch (PrinterException pe) {
                            Debug.showFriendlyErrorDialog(JPrintDialog.this, pe);
                        }
                    }
                }.start();
            }
        }
    };

    public JPrintDialog(PluginContext context) {
        super(context.getJAlbumContext().getFrame(), "Print", true);
        this.context = context;
        init();
    }

    private void setPageFormat(PageFormat pf) {
        this.pageFormat = pf;
    }

    private void init() {
        engine = context.getJAlbumContext().getEngine();
        try {
            engine.init();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

        printJob = PrinterJob.getPrinterJob();
        printJob.setJobName("jAlbum printing");
        pageFormat = printJob.defaultPage();
        pageFormat.setOrientation(prefs.getInt("pageOrientation", PageFormat.LANDSCAPE));

        final ListCellRenderer renderer = pageLayout.getRenderer();
        pageLayout.setRenderer(new ListCellRenderer<PageLayout>() {

            public Component getListCellRendererComponent(JList<? extends PageLayout> list, PageLayout value, int index, boolean isSelected, boolean cellHasFocus) {
                JLabel label = ((JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus));
                label.setText(value.toString(pageFormat.getOrientation()));
                return label;
            }

        });

        pageLayout.setSelectedItem(PageLayout.valueOf(prefs.get("pageLayout", PageLayout.twelve.name())));
        pageLayoutIndicator = new JPageLayout(pageFormat, getPageLayout(), gap);
        pageLayoutIndicator.setToolTipText("Page layout");
        new StateMonitor() {

            @Override
            public void onChange() {
                pageLayoutIndicator.setPageLayout(getPageLayout());
            }

        }.add(pageLayout).done();

        Container content = getContentPane();
        content.setLayout(new BorderLayout());

        // Make sure ESC cancels
        KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        JComponent pane = getLayeredPane(); // So pressing escape won't conflict with menus
        pane.registerKeyboardAction(closeAction, "close", ks, JComponent.WHEN_IN_FOCUSED_WINDOW);

        JPanel contentPanel = new JPanel(new BorderLayout());
        setContentPane(contentPanel);
        contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 4, 8));

        // UI layout
        ControlPanel controls = new ControlPanel();
        controls.add(new JLabel("Images per page"));
        controls.add("tab", pageLayout);
        controls.add("br", showFilenames);
        controls.add("br", showCaptions);

        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
        buttonPanel.add(new JButton(pageSetupAction));
        JButton printButton = new JButton(printAction);
        buttonPanel.add(printButton);

        contentPanel.add(controls, BorderLayout.CENTER);
        contentPanel.add(new JLabel(bigIcon), BorderLayout.WEST);
        contentPanel.add(pageLayoutIndicator, BorderLayout.EAST);
        contentPanel.add(buttonPanel, BorderLayout.SOUTH);

        getRootPane().setDefaultButton(printButton);
        setResizable(false);

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                closeAction.actionPerformed(null);
            }
        });
        pack();
    }

    private PageLayout getPageLayout() {
        return (PageLayout) pageLayout.getSelectedItem();
    }

    private int pass = 1;
    private int lastPageIndex = -1;

    /**
     * Printable interface
     */
    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
        int rows = getPageLayout().getRows();
        int cols = getPageLayout().getCols();
        if (pageFormat.getOrientation() == PageFormat.PORTRAIT) {
            int t = rows;
            rows = cols;
            cols = t;
        }
        int imagesPerPage = rows * cols;

        if (progressMonitor.isCanceled()) {
            System.out.println("Cancelling print");
            printJob.cancel();
            return (NO_SUCH_PAGE);
//            throw new PrinterAbortException(); // Crashes when printing on non-AWT thread (needs to do so to have smooth progress dialog)
        }

        if (pageIndex == lastPageIndex) {
            pass++;
        } else {
            lastPageIndex = pageIndex;
            pass = 1;
        }

        int totalPages = (int) Math.ceil((double) images.size() / imagesPerPage);
        if (pageIndex >= totalPages) {
            progressMonitor.close();
            return (NO_SUCH_PAGE);
        }

        Graphics2D g = (Graphics2D) graphics;
        progressMonitor.setNote("Page " + (pageIndex + 1) + " of " + totalPages + ". Pass " + pass);

        AffineTransform transform = g.getTransform();
        g.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
        Dimension printable = new Dimension((int) (pageFormat.getImageableWidth()), (int) pageFormat.getImageableHeight());
        Dimension tile = new Dimension((printable.width - gap * (cols - 1)) / cols, (printable.height - gap * (rows - 1)) / rows);
        int startIndex = pageIndex * imagesPerPage;
        try {
            for (int row = 0; row < rows; row++) {
                for (int col = 0; col < cols; col++) {
                    int imageNum = startIndex + row * cols + col;
                    AlbumObject ao = images.get(imageNum);
                    AffineTransform oldTransform = g.getTransform();
                    g.translate((double) printable.width * col / cols, (double) printable.height * row / rows);
                    Shape oldClip = g.getClip();
                    g.setClip(0, 0, tile.width, tile.height);
                    printTile(g, ao, tile);
                    g.setClip(oldClip);
                    g.setTransform(oldTransform);
                    progressMonitor.setProgress(imageNum);
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace(System.err);
        } catch (IndexOutOfBoundsException ex) {
        }
        g.setTransform(transform);
        return (PAGE_EXISTS);
    }

    /**
     * Generate a "|" separated String of components like filename and caption
     */
    private String getText(AlbumObject ao) {
        List<String> texts = new ArrayList<String>();
        if (showFilenames.isSelected()) {
            texts.add(ao.getName());
        }
        if (showCaptions.isSelected()) {
            String comment = stripHtml.replace(ao.getComment()).replaceAll("\\<.*?\\>", "");;
            if (comment.length() > 0) {
                texts.add(comment);
            }
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < texts.size(); i++) {
            if (i > 0) {
                sb.append(" | ");
            }
            sb.append(texts.get(i));
        }
        if (sb.length() > 0) {
            return sb.toString();
        } else {
            return null;
        }
    }

//    private AttributedString getLabel(AlbumObject ao, Font font) {
//        String fileName = showFilenames.isSelected() ? ao.getName() + " " : "";
//        String comment = showCaptions.isSelected() ? stripHtml.replace(ao.getComment()).replaceAll("\\<.*?\\>", "") : "";
//        
//        String label = fileName + comment;
//        if (label.isEmpty()) {
//            return null;
//        }
//        
//        AttributedString as = new AttributedString(label);
//        as.addAttribute(TextAttribute.FAMILY, font.getName()); // Yes, it shoud not be getFamily
//        as.addAttribute(TextAttribute.SIZE, font.getSize2D()); // Yes, it shoud not be getFamily
//
//        if (!fileName.isEmpty()) {
//            as.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_EXTRABOLD, 0, fileName.length());
//        }
//        return as;
//    }
    /**
     * Scale to fit without changing aspect ratio
     */
    static Dimension getScaledSize(Dimension imageDim, Dimension boundingBox) {
        // Make image fit within bounding box without changing aspect ratio
        // Do not enlarge image
        if ((imageDim.width <= boundingBox.width) && (imageDim.height <= boundingBox.height)) {
            return imageDim;
        }

        double widthScale = (double) imageDim.width / boundingBox.width;
        double heightScale = (double) imageDim.height / boundingBox.height;
        double maxScale = Math.max(widthScale, heightScale);
        return new Dimension((int) (imageDim.width / maxScale + 0.5), (int) (imageDim.height / maxScale + 0.5));
    }

    private void printTile(Graphics2D g, AlbumObject ao, Dimension tile) throws IOException {
//        g.drawRect(0, 0, tile.width, tile.height);
        BufferedImage image = (BufferedImage) ao.loadImage(true, new Dimension(tile.width * 2, tile.height * 2));
        AlbumImage ai = new AlbumImage(image, engine);
        try {
            ai = ai.applyFilters(JAFilter.ALL_PRESCALE_STAGE);
            ai = ai.applyFilters(JAFilter.ALL_POSTSCALE_STAGE);
            image = (BufferedImage) ai.getImage();
        } catch (Exception ex) {
            ex.printStackTrace(System.err);
            return;
        }

        final Dimension originalDim = new Dimension(image.getWidth(), image.getHeight());
        Dimension imageBox = (Dimension) tile.clone();
        Font f = new Font(Font.DIALOG, Font.PLAIN, 16 - (int) (1.5 * getPageLayout().getCols()));
        g.setFont(f);
        String label = getText(ao);
        TextBox textBox = new TextBox(label, g);
//        TextBox textBox = new TextBox(getText(ao), g);
        Dimension tb = textBox.getSize(tile.width);
        imageBox.height -= tb.getHeight();

        Dimension imageDim = getScaledSize(originalDim, imageBox);
        float yoff = 0; // Top aligned
//        float yoff = (tile.height - (imageDim.height + tb.height)) / 2; // Centered
        float xoff = (tile.width - imageDim.width) / 2;

        if (label != null) {
            g.setColor(Color.black);
            textBox.draw(xoff + (imageDim.width - tb.width) / 2, yoff + imageDim.height, tile.width);
        }
        g.drawImage(image, (int) xoff, (int) yoff, imageDim.width, imageDim.height, null);
//        System.out.println("Drawing image dim " + imageDim);
//        System.out.println("Using resolution " + imageDim.width * 300 / 72 + " x " + imageDim.height * 300 / 72);
    }
}
